/*
 *
 *  *    Copyright © OpenAtom Foundation.
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *         http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.inspur.edp.commonmodel.engine.core.data;


import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.inspur.edp.cef.core.data.AccessorCollection;
import com.inspur.edp.cef.core.data.ChildEntityAccessor;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.entity.accessor.base.AccessorBase;
import com.inspur.edp.cef.entity.accessor.base.AccessorComparer;
import com.inspur.edp.cef.entity.accessor.dataType.ValueObjAccessor;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IChildEntityData;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IEntityDataCollection;
import com.inspur.edp.cef.spi.entity.resourceInfo.DataTypeResInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.EntityResInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefEntityResInfoImpl;
import com.inspur.edp.commonmodel.engine.api.data.IEngineEntityData;
import com.inspur.edp.commonmodel.engine.core.common.CMUtil;
import com.inspur.edp.commonmodel.engine.core.data.serializer.CommEngineDataSerializer;
import com.inspur.edp.commonmodel.engine.core.exception.CMEngineCoreException;
import com.inspur.edp.commonmodel.engine.core.exception.ErrorCodes;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import lombok.var;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@JsonSerialize(using = CommEngineDataSerializer.class)
public class EngineChildAccessor extends ChildEntityAccessor implements IChildEntityData, IEngineEntityData {

    private final boolean isReadonly;
    protected HashMap<String, IEntityDataCollection> childs;
    @JSONField(serialize = false)
    protected final IGspCommonObject node;
    protected final CefEntityResInfoImpl resInfo;
    protected Map<String, Object> nestedValues;

    @Deprecated
    public EngineChildAccessor(IGspCommonObject node, IEntityData data) {
        this(node, data, null);
    }

    public EngineChildAccessor(IGspCommonObject node, IEntityData data, CefEntityResInfoImpl resInfo) {
        this(node, data, resInfo, false);
    }

    public EngineChildAccessor(IGspCommonObject node, IEntityData data, CefEntityResInfoImpl resInfo,
                               boolean isReadonly) {
        super(data);
        Objects.requireNonNull(node, "node");
        this.node = node;
        this.resInfo = resInfo;
        this.isReadonly = isReadonly;
        initialize();
    }

    @Override
    public String getNodeCode() {
        return node.getCode();
    }

    @Override
    public final List<String> getPropertyNames() {
        return CMUtil.buildNodePropertyNames(node, getElementPredicate());
    }

    @Override
    protected HashMap<String, IEntityDataCollection> innerGetChilds() {
        return new HashMap<>(childs);
    }

    @Override
    protected ICefData innerCreateChild(String childCode) {
        Objects.requireNonNull(childCode, "childCode");

        IGspCommonObject childNode = CMUtil.checkNodeExists(node, childCode, getNodePredicate());
        EntityResInfo childResInfo = resInfo != null ? resInfo.getChildEntityResInfo(childNode.getCode()) : null;
        return new EngineChildAccessor(childNode,
                (IEntityData) getInnerData().createChild(childCode),
                (CefEntityResInfoImpl) childResInfo);
    }

    @Override
    protected IEntityDataCollection innerCreateAndSetChildCollection(String s) {
        IGspCommonObject child = CMUtil.checkNodeExists(node, s, getNodePredicate());
        CefEntityResInfoImpl childResInfo =
                resInfo != null ? (CefEntityResInfoImpl) resInfo.getChildEntityResInfo(child.getCode())
                        : null;
        AccessorCollection collection = new AccessorCollection(null,
                childResInfo,
                () -> new EngineChildData(child, childResInfo),
                (data) -> new EngineChildAccessor(child, data, childResInfo));
        collection.setParent(this);
        childs.put(s, collection);
        return collection;
    }

    @Override
    public boolean getIsReadonly() {
        return isReadonly;
    }

    @Override
    protected Object innerGetValue(String labeldId) {
        Objects.requireNonNull(labeldId, "labeldId");

        var element = CMUtil.checkElementExists(node, labeldId, getElementPredicate());
        if (element.getIsUdt() || element.getObjectType() == GspElementObjectType.DynamicProp) {
            return nestedValues.get(element.getLabelID());
        } else {
            return getInnerData().getValue(labeldId);
        }
    }

    @Override
    protected void innerSetValue(String labeldId, Object o) {
        Objects.requireNonNull(labeldId, "labeldId");

        var element = CMUtil.checkElementValue(node, labeldId, o, getElementPredicate());
        if (element.getIsUdt() || element.getObjectType() == GspElementObjectType.DynamicProp) {
            DataAccUtil.throwSetUdtValue(element.getLabelID());
        }
        Object orgValue = getInnerData().getValue(labeldId);
        super.raisePropertyChanging(labeldId, o, orgValue);
        if (!AccessorComparer.equals(orgValue, o)) {
            super.tryCopy();
            getInnerData().setValue(labeldId, o);
        }
    }

    @Override
    protected AccessorBase createNewObject() {
        return new EngineChildAccessor(node, null, resInfo);
    }

    @Override
    public void setID(String s) {
        throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1001);
    }

    @Override
    public void copyCore(com.inspur.edp.cef.entity.accessor.base.AccessorBase accessor) {
        EngineChildAccessor result = (EngineChildAccessor) accessor;
        result.childs = new HashMap<String, IEntityDataCollection>(childs.size());
        for (Map.Entry<String, IEntityDataCollection> pair : childs.entrySet()) {
            result.childs.put(pair.getKey(), ((AccessorCollection) pair.getValue()).copy(result));
        }
        if (nestedValues != null) {
            result.nestedValues = new HashMap<>(nestedValues.size(), 1);
            nestedValues.entrySet().forEach(pair -> {
                var nestedValue = (ValueObjAccessor) ((ICefData) pair.getValue()).copy();
                nestedValue.setBelongBuffer(result);
                result.nestedValues.put(pair.getKey(), nestedValue);
            });
        }
    }

    @Override
    protected ICefData innerCopySelf() {
        ICefData result = (ICefData) super.innerCopySelf();
        if (nestedValues != null) {
            nestedValues.entrySet().forEach(pair -> {
                result.setValue(pair.getKey(), ((ICefData) pair.getValue()).copySelf());
            });
        }
        return result;
    }

    @Override
    protected void onInnerDataChange() {
        super.onInnerDataChange();
        initializeNested();
    }

    private void initialize() {
        initializeNested();
        initializeChildCollection();
    }

    protected void initializeNested() {
        if (getInnerData() == null) {
            nestedValues = null;
            return;
        }

        nestedValues = new HashMap<>();
        CMUtil.accInitNestedValue(this, node, nestedValues, getElementPredicate(), resInfo);
    }

    protected void initializeChildCollection() {
        Stream<IGspCommonObject> stream;
        if(node.getContainChildObjects()!=null){
            stream = node.getContainChildObjects().stream();
        }else{
            stream = Stream.empty();
        }
        Predicate<IGspCommonObject> predicate = getNodePredicate();
        if (predicate!= null) {
            stream = stream.filter(predicate);
        }
        List<IGspCommonObject> childNodes = stream.collect(Collectors.toList());
        childs = new HashMap<>(childNodes.size(), 1);
        for (IGspCommonObject child : childNodes) {
            CefEntityResInfoImpl childResInfo =
                    resInfo != null ? (CefEntityResInfoImpl) resInfo.getChildEntityResInfo(child.getCode())
                            : null;
            AccessorCollection collection = new AccessorCollection(this,
                    childResInfo,
                    () -> new EngineChildData(child, childResInfo),
                    (data) -> new EngineChildAccessor(child, data, childResInfo));
            childs.put(child.getCode(), collection);
        }
    }

    @Override
    protected DataTypeResInfo getResInfo() {
        return resInfo;
    }

    private String parentLabelId;

    private String getParentLabelId() {
        if (parentLabelId == null) {
            parentLabelId = node.findElement(node.getKeys().get(0).getSourceElement()).getLabelID();
        }
        return parentLabelId;
    }

    @Override
    public String getParentID() {
        Object value = getValue(getParentLabelId());
        return CMUtil.convert2String(value);
    }

    @Override
    public void setParentID(String s) {
        setValue(getParentLabelId(), s);
    }

    //#region virtual
    protected Predicate<IGspCommonField> getElementPredicate() {
        return null;
    }

    protected Predicate<IGspCommonObject> getNodePredicate() {
        return null;
    }

    @Override
    @JSONField(serialize = false)
    public final IGspCommonObject getNode() {
        return node;
    }
    //#endregion
}
